#include "gtest/gtest.h"

#include "helper/scope/ScopeGuard.h"
#include "helper/scope/ScopableAdapter.h"
#include "helper/scope/ScopeGuardWrapper.h"

#include "helper/unittest/Tracer.h"
using helper::unittest::Tracer;

using namespace helper;
using namespace scope;

TEST(ScopeGuardTest, testIndepentScopableObject){
    ScopableAdapter<Tracer> sa(
      [](Tracer & tracer){tracer.reset().ctor();}, 
      [](Tracer & tracer){tracer.dtor();}
    );
    Tracer tracer;
    {
        ScopeGuard<Tracer> sc(tracer, sa);
    }
    EXPECT.reset().ctor().dtor();
    ASSERT_EQ(EXPECT, tracer);
}

TEST(ScopeGuardTest, testVoid){
    ScopableAdapter<void> sa(
        [](){TRACER.reset().ctor();}, 
        [](){TRACER.dtor();}
    );
    {
        ScopeGuard<void> sg(sa);
    }
    EXPECT.reset().ctor().dtor();
    ASSERT_EQ(EXPECT,TRACER);
}
class TracerAdapter : public IScopable<void>{
public:
    ~TracerAdapter() override{
    }
    Tracer & getTracer(){
        return tracer;
    }
    int enter() override{
        tracer.ctor();
        return 0;
    }
    int exit() override {
        tracer.dtor();
        return 0;
    }
private:
    Tracer tracer;
};
TEST(ScopeGuardTest, testVoid_CustomeIScopableVoid){
    TracerAdapter ta;
    {
        ScopeGuard<void> sc(ta);
    }
    EXPECT.reset().ctor().dtor();
    ASSERT_EQ(EXPECT, ta.getTracer());
}


TEST(ScopeGuardWrapperTest, testIf){
    Tracer tracer;
    {
        ScopeGuardWrapper<Tracer> sc(tracer, 
            [](Tracer & tracer){tracer.ctor();}, 
            [](Tracer & tracer){tracer.dtor();}
        );
    }
    EXPECT.reset().ctor().dtor();
    ASSERT_EQ(tracer, EXPECT);
}

TEST(ScopeGuardWrapperTest, testVoid){
    Tracer tracer;
    {
        ScopeGuardWrapper<void> sgw(
            [&tracer](){tracer.reset().ctor();},
            [&tracer](){tracer.dtor();}
        );
    }
    EXPECT.reset().ctor().dtor();
    ASSERT_EQ(EXPECT, tracer);
}


class MockMutex {
public:
    MockMutex() : locked(false) {}
    void lock(){
        locked = true;
    }
    void unlock(){
        locked = false;
    }
    bool isLocked(){
        return locked;
    }
private:
    bool locked;
};
TEST(ScopeGuardWrapperTest, testLambdaWithLockObject){
    MockMutex mutex;
    {
        ScopeGuardWrapper<MockMutex> sgw(mutex, 
            [](MockMutex & mutex) { mutex.lock();},
            [](MockMutex & mutex) { mutex.unlock();}
        );
        ASSERT_TRUE(mutex.isLocked());
    }
    ASSERT_FALSE(mutex.isLocked());

}
TEST(ScopeGuardWrapperTest, testScopePtr){
    int * ptr = (int*)1;
    {
        ScopeGuardWrapper<int *> sgw(ptr, 
            [](int * & ptr) { ptr =  new int(1);},
            [](int * & ptr) { delete ptr; ptr = nullptr;}
        );
        ASSERT_EQ(1, *ptr);
    }
    ASSERT_TRUE(nullptr == ptr);
}


namespace test {


class NamedStopWatch : public IScopable<const std::string> {
public:
    ~NamedStopWatch()override{}
    int enter(const std::string & name) override{
        //std::cout << "enter->name:" << name << std::endl;
        return -1;
    }
    int exit(const std::string & name) override {
        //std::cout << "exit->name:" << name << std::endl;
        return 0;
    }
};
class NamedLap : public ScopeGuard<const std::string> {
public:
    NamedLap(const std::string & name, IScopable<const std::string> & scopable)
    :ScopeGuard<const std::string>(name, scopable)
    {
    }
};

}// end of namespace test
TEST(ScopeGuardWrapperTest, testStopWatch){
    test::NamedStopWatch sw;
    {
        test::NamedLap lap("code_block_1", sw);
    }
}


// More test case see helper/watch/StopWatch Series Classes
